#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <cerrno>
#include <functional>
#include <unordered_map>
#include "Log.hpp"
#include "Epoller.hpp"
#include "Socket.hpp"
#include "Comm.hpp"

class Connection;
class TcpServer;

using func_t = std::function<void(std::weak_ptr<Connection>)>; //函数对象
using except_func = std::function<void(std::weak_ptr<Connection>)>;

uint32_t EVENT_IN = (EPOLLIN | EPOLLET); //让epoll模型以ET方式运行
uint32_t EVENT_OUT = (EPOLLOUT | EPOLLET);
// 除了设置ET方式，还得将文件描述符设置为非阻塞，在Sock里实现

const static int g_buffer_size = 128;


//要保证每个fd都有输入输出缓冲区
class Connection
{
public:
    Connection(int sock)
        : _sock(sock) 
    {}

    void SetHandler(func_t recv_cb, func_t send_cb, except_func except_cb) //设置回调逻辑
    {
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _except_cb = except_cb;
    }

    int SockFd()
    {
        return _sock;
    }

    void AppendInBuffer(const std::string& info)
    {
        _inbuffer += info;
    }

    void AppendOutBuffer(const std::string& info)
    {
        _outbuffer += info;
    }

    std::string& InBuffer() //测试用，不加const，可以让上层在反序列化时自动移除inbuffer里的相关内容，因为那些接口用的都是引用
    {
        return _inbuffer;
    }
    std::string& OutBuffer() //测试用
    {
        return _outbuffer;
    }
    void SetWeakPtr(std::weak_ptr<TcpServer> tcp_server_ptr)
    {
        _tcp_server_ptr = tcp_server_ptr;
    }
    
    ~Connection() {}
public:
    func_t _recv_cb; //读回调，一旦有读事件就绪了，就调用这个函数
    func_t _send_cb; //写回调
    except_func _except_cb; 

    //添加一个指向TcpServer的回指指针
    std::weak_ptr<TcpServer> _tcp_server_ptr;

    std::string _ip;
    uint16_t _port;

private:
    int _sock; //链接对应的套接字
    std::string _inbuffer; //暂时用string来充当缓冲区，有bug因为string无法处理二进制流
    std::string _outbuffer; //可以用vector，但是vector单独搞缓冲区有点麻烦
};

class TcpServer : public std::enable_shared_from_this<TcpServer>, public nocopy
{
    static const int num = 64;
public:
    TcpServer(uint16_t port, func_t OnMessage)
        : _port(port)
        , _OnMessage(OnMessage)
        , _quit(true)
        , _epoller_ptr(new Epoller())
        , _listensock_ptr(new Sock())
    {}

    void Init()
    {
        _listensock_ptr->Socket();
        SetNonBlockOrDie(_listensock_ptr->Fd()); //把文件描述符设置为非阻塞
        _listensock_ptr->Bind(_port);
        _listensock_ptr->Listen();
        lg(Info, "create listen socket success: %d", _listensock_ptr->Fd());
        AddConnection(_listensock_ptr->Fd(), EVENT_IN, std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr); 
    }

    void AddConnection(int sock, uint32_t event, func_t recv_cb, func_t send_cb, except_func except_cb, const std::string &ip = "0.0.0.0", uint16_t port = 0)
    {
        //1，还要给listensock也建立一个connection对象，然后构建键值对，添加进unordered_map中
        std::shared_ptr<Connection> new_connection(new Connection(sock)); //为每个sock构建connection对象，同时把this指针传过去填充connection的回指指针
        //new_connection->SetWeakPtr(shared_from_this());
        new_connection->SetHandler(recv_cb, send_cb, except_cb); //设置三个回调函数
        new_connection->SetWeakPtr(shared_from_this());
        new_connection->_ip = ip;
        new_connection->_port = port;

        //2，添加到unordered_map中
        _connections.insert(std::make_pair(sock, new_connection));

        //3，除了添加进内核中，告诉内核要关心哪个文件描述符的哪个事件
        _epoller_ptr->EpollUpdate(EPOLL_CTL_ADD, sock, event); //将listensock添加进内核epoll模型
    } 
    void Accepter(std::weak_ptr<Connection> conn)
    {
        auto connection = conn.lock();
        //由于是ET模式，需要死循环，不断进行Accept
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = ::accept(connection->SockFd(), (struct sockaddr *)&peer, &len);
            if(sock > 0) //获取连接成功
            {
                uint16_t peerport = ntohs(peer.sin_port);
                char ipbuf[128];
                inet_ntop(AF_INET, &peer.sin_addr.s_addr, ipbuf, sizeof(ipbuf)); 
                lg(Debug, "get a new client, get info-> [%s:%d], sockfd : %d", ipbuf, peerport, sock);
                SetNonBlockOrDie(sock);
                AddConnection(sock, EVENT_IN,
                              std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                              std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                              std::bind(&TcpServer::Excepter, this, std::placeholders::_1),
                              ipbuf, peerport); // TODO
            }
            else
            {
                if (errno == EWOULDBLOCK) //读完了，就是已经把底层所有数据全拿上来了
                    break;
                else if (errno == EINTR) //数据可读，但是被信号中断等操作终止了，继续读
                    continue;
                else
                    break;
            }
        }
    }
public: //下面是三个回调函数，对应着读，写，异常的处理方法
    //我们不应该关心，服务器只要IO数据就可以，有没有读完，报文的格式细节等不用管，只要管把数据收上来即可，怎么处理是上层的事情
    void Recver(std::weak_ptr<Connection> conn)
    {
        if(conn.expired()) return;
        auto connection = conn.lock();
        int sock = connection->SockFd();
        while(true)
        {
            char buffer[g_buffer_size];
            memset(buffer, 0, sizeof(buffer));
            ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0); // 非阻塞读取
            if (n > 0) //读成功了，就无脑往当前套接字的inbuffer里面拼接内容
            {
                connection->AppendInBuffer(buffer);
            }
            else if (n == 0) //对方把连接关了
            {
                lg(Info, "sockfd: %d, client info %s:%d quit...", sock, connection->_ip.c_str(), connection->_port);
                connection->_except_cb(connection); //异常处理流程
                return;
            }
            else //出错了
            {
                if (errno == EWOULDBLOCK) //说明读完了，不处理了
                    break;
                else if (errno == EINTR) //读的时候意外被某些异常信号中断了，所以应该继续读取
                    continue;
                else
                {
                    lg(Warning, "sockfd: %d, client info %s:%d recv error...", sock, connection->_ip.c_str(), connection->_port);
                    connection->_except_cb(connection); //也进入异常流程
                    return;
                }
            }
        }
        // 数据有了，但是不一定全，所以：1. 检测 2. 如果有完整报文，就处理  3. 如果没有完整报文，就
        _OnMessage(connection); // 你读到的sock所有的数据都在connection的inbuffer缓冲区里
    }
    void Sender(std::weak_ptr<Connection> conn)
    {
        if(conn.expired()) return;
        auto connection = conn.lock();
        auto &outbuffer = connection->OutBuffer();
        while(true)
        {

            ssize_t n = send(connection->SockFd(), outbuffer.c_str(), outbuffer.size(), 0);
            if(n > 0) //发送完毕
            {
                outbuffer.erase(0, n); //从0开始到n，表示把已经发送的字符移除掉
                if(outbuffer.empty()) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
                    break; //如果为空，表示全发完了，就break退出，如果没法玩，就循环继续发
            }
            else if(n == 0) //可能因为一些原因，buffer里面啥也没有，那么我们就什么也不做
            {
                return;
            }
            else //发送出错
            {
                if(errno == EWOULDBLOCK) break; //底层缓冲区空间不够了，但上层还有数据要发，所以不能发了，就break
                else if(errno == EINTR) continue; //被信号中断了
                else //发出错了
                {
                    lg(Warning, "sockfd: %d, client info %s:%d send error...", connection->SockFd(), connection->_ip.c_str(), connection->_port);
                    connection->_except_cb(connection); //进入异常处理
                    return;
                }   
            }

        }

        //走到这里后，有两种情况
        if(!outbuffer.empty()) //写事件结束，但是还有数据没写完，开启对写事件的关心
        {
            EnableEvent(connection->SockFd(), true, true);
        }
        else // 写事件结束，数据也写完了，关闭对写事件的关心
        {
            EnableEvent(connection->SockFd(), true, false);
        }

    }

    void EnableEvent(int sock, bool readable, bool writeable)
    {
        uint32_t events = 0;
        events |= ((readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET); //如果为true就设置对应事件，为false就设为0，这三个三位操作符都是一样
        _epoller_ptr->EpollUpdate(EPOLL_CTL_MOD, sock, events); //对sock修改，改为events
    }

    void Excepter(std::weak_ptr<Connection> conn) //异常处理
    {

        if(conn.expired()) return;
        auto connection = conn.lock();

        int fd = connection->SockFd();
        lg(Warning, "Excepter hander sockfd: %d, client info %s:%d excepter handler", connection->SockFd(), connection->_ip.c_str(), connection->_port);
        //1，移除对特定fd的关心
        // EnableEvent(connection->SockFd(), false, false); //可以这样干，但是不推荐
        _epoller_ptr->EpollUpdate(EPOLL_CTL_DEL, fd, 0);

        //2，关闭异常的fd
        close(fd);
        lg(Debug,"close %d done...\n", fd);

        //3，从unordered_map中移除
        lg(Debug, "remove %d from _connections...\n", fd);
        //auto iter = _connections.find(fd);
        //if(iter == _connections.end()) return;
        _connections.erase(fd); //根据fd的key值去移除
    }

    bool IsConnectionSafe(int fd) //检测链接是否正常
    {
        auto iter = _connections.find(fd);
        if(iter == _connections.end()) return false; //对应的链接不存在
        else return true; //对应的链接还在
    }

    void Dispatcher(int timeout) //获取一次
    {
        int n = _epoller_ptr->EpollerWait(revs, num, timeout);
        for(int i = 0; i < n; i++)
        {
            uint32_t events = revs[i].events;
            int sock = revs[i].data.fd;
            //有任何异常，都转化为读写事件异常，未来只需要进行读写问题了，在读写逻辑处理异常就好了，体现高内聚
            //if(events & EPOLLERR) events |= (EPOLLIN | EPOLLOUT); //有异常
            //if(events & EPOLLHUP) events |= (EPOLLIN | EPOLLOUT); //对方把描述符挂断了

            if((events & EPOLLIN) && IsConnectionSafe(sock)) //读事件就绪
            {
                if(_connections[sock] -> _recv_cb) //如果读事件就绪，并且在unordered_map里存在，并且回调方法设置了，就调用这个回调方法
                    _connections[sock] -> _recv_cb(_connections[sock]);
            }
            if((events & EPOLLOUT) && IsConnectionSafe(sock)) //写事件就绪
            {
                if(_connections[sock] -> _send_cb) //如果写事件就绪，并且在unordered_map里存在，并且回调方法设置了，就调用这个回调方法    
                    _connections[sock] -> _send_cb(_connections[sock]); 
            }
        }
    }

    void Loop()
    {
        _quit = false;
        while(!_quit) //获取到了已经就绪的事件
        {
            Dispatcher(-1); //进行事件派发
            PrintConnection();
        }        
        _quit = true;
    }
    void PrintConnection()
    {
        std::cout << "_connectons fd list: ";
        for(auto& connection : _connections)
        {
            std::cout << connection.second->SockFd();
            std::cout << ", inbuffer: " << connection.second->InBuffer().c_str();
        }
        std::cout << std::endl;
    }

    ~TcpServer()
    {

    }

private:
    std::shared_ptr<Epoller> _epoller_ptr; //负责等待文件描述符
    std::shared_ptr<Sock> _listensock_ptr; //监听套接字
    std::unordered_map<int, std::shared_ptr<Connection>> _connections; //从一个文件描述符到链接的映射，用一个unordered_map管理所有的链接，先描述，再组织

    struct epoll_event revs[num]; //表示要关心的事件
    uint16_t _port;
    bool _quit;

    //让上层处理信息
    func_t _OnMessage;
};